// Copyright 2021 Tencent Inc. All rights reserved.
//
// 商家转账对外API（2025年1月15日升级版本）
//
// 商家转账支持微信商户向微信用户转账，为商户提供免费、安全的转账服务。支持集成到商家自有业务系统，需有研发能力可接入。
// 商户需在用户收款流程中，拉起微信官方页面，由用户确认收款方式后方可成功转账，资金实时到账，转账成功的资金不支持退回。
// [https://pay.weixin.qq.com/doc/v3/merchant/4012711988](https://pay.weixin.qq.com/doc/v3/merchant/4012711988)
//
// API version: 1.0.0

// Code generated by WechatPay APIv3 Generator based on [OpenAPI
// Generator](https://openapi-generator.tech); DO NOT EDIT.

package com.wechat.pay.java.service.mchtransfer;

import com.java.utils.WXPayUtility;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.cipher.PrivacyDecryptor;
import com.wechat.pay.java.core.cipher.PrivacyEncryptor;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.*;
import com.wechat.pay.java.service.mchtransfer.model.*;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;

import static com.wechat.pay.java.core.http.UrlEncoder.urlEncode;
import static com.wechat.pay.java.core.util.GsonUtil.toJson;
import static java.util.Objects.requireNonNull;

/** MchTransferService服务 */
public class MchTransferService {

    private final HttpClient httpClient;
    private final HostName hostName;
    private final PrivacyEncryptor encryptor;
    private final PrivacyDecryptor decryptor;

    private MchTransferService(HttpClient httpClient, HostName hostName, PrivacyEncryptor encryptor, PrivacyDecryptor decryptor) {
        this.httpClient = requireNonNull(httpClient);
        this.hostName = hostName;
        this.encryptor = requireNonNull(encryptor);
        this.decryptor = requireNonNull(decryptor);
    }

    /** MchTransferService构造器 */
    public static class Builder {

        private HttpClient httpClient;
        private OkHttpClient okHttpClient;
        private HostName hostName;
        private PrivacyEncryptor encryptor;
        private PrivacyDecryptor decryptor;

        public Builder config(Config config) {
            this.httpClient = new DefaultHttpClientBuilder().config(config).build();
            this.encryptor = config.createEncryptor();
            this.decryptor = config.createDecryptor();
            return this;
        }

        public Builder hostName(HostName hostName) {
            this.hostName = hostName;
            return this;
        }

        public Builder httpClient(HttpClient httpClient) {
            this.httpClient = httpClient;
            return this;
        }

        public Builder encryptor(PrivacyEncryptor encryptor) {
            this.encryptor = encryptor;
            return this;
        }

        public Builder decryptor(PrivacyDecryptor decryptor) {
            this.decryptor = decryptor;
            return this;
        }

        public MchTransferService build() {
            return new MchTransferService(httpClient, hostName, encryptor, decryptor);
        }
    }

    /**
     * 微信单号查询转账单
     *
     * @param request 请求参数
     * @return TransferBillEntity
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public TransferBillEntity getTransferBillByNo(GetTransferBillByNoRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/{transfer_bill_no}";

        // 添加 path param
        requestPath = requestPath.replace("{" + "transfer_bill_no" + "}", urlEncode(request.getTransferBillNo()));

        if (this.hostName != null) {
            requestPath = requestPath.replaceFirst(HostName.API.getValue(), hostName.getValue());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest =
                new HttpRequest.Builder()
                        .httpMethod(HttpMethod.GET)
                        .url(requestPath)
                        .headers(headers)
                        .build();
        HttpResponse<TransferBillEntity> httpResponse = httpClient.execute(httpRequest, TransferBillEntity.class);
        return httpResponse.getServiceResponse().cloneWithCipher(decryptor::decrypt);
    }

    /**
     * 商户单号查询转账单
     *
     * @param request 请求参数
     * @return TransferBillEntity
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public TransferBillEntity getTransferBillByOutNo(GetTransferBillByOutNoRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}";

        // 添加 path param
        requestPath = requestPath.replace("{" + "out_bill_no" + "}", urlEncode(request.getOutBillNo()));

        if (this.hostName != null) {
            requestPath = requestPath.replaceFirst(HostName.API.getValue(), hostName.getValue());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest =
                new HttpRequest.Builder()
                        .httpMethod(HttpMethod.GET)
                        .url(requestPath)
                        .headers(headers)
                        .build();
        HttpResponse<TransferBillEntity> httpResponse = httpClient.execute(httpRequest, TransferBillEntity.class);
        return httpResponse.getServiceResponse().cloneWithCipher(decryptor::decrypt);
    }

    /**
     * 发起商家转账
     *
     * @param request 请求参数
     * @return InitiateMchTransferResponse
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public InitiateMchTransferResponse initiateMchTransfer(InitiateMchTransferRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills";

        // 加密敏感信息
        InitiateMchTransferRequest realRequest = request.cloneWithCipher(encryptor::encrypt);

        if (this.hostName != null) {
            requestPath = requestPath.replaceFirst(HostName.API.getValue(), hostName.getValue());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.WECHAT_PAY_SERIAL, encryptor.getWechatpaySerial());
        HttpRequest httpRequest = new HttpRequest.Builder()
                .httpMethod(HttpMethod.POST)
                .url(requestPath)
                .headers(headers)
                .body(createRequestBody(realRequest))
                .build();
        HttpResponse<InitiateMchTransferResponse> httpResponse = httpClient.execute(httpRequest, InitiateMchTransferResponse.class);
        return httpResponse.getServiceResponse();
    }

    /**
     * 撤销商家转账
     *
     * @param request 请求参数
     * @return CancelMchTransferResponse
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public CancelMchTransferResponse cancelMchTransfer(CancelMchTransferRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel";

        // 添加 path param
        requestPath = requestPath.replace("{" + "out_bill_no" + "}", urlEncode(request.getOutBillNo()));

        if (this.hostName != null) {
            requestPath = requestPath.replaceFirst(HostName.API.getValue(), hostName.getValue());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.WECHAT_PAY_SERIAL, encryptor.getWechatpaySerial());
        HttpRequest httpRequest = new HttpRequest.Builder()
                .httpMethod(HttpMethod.POST)
                .url(requestPath)
                .headers(headers)
                .body((RequestBody) okhttp3.RequestBody.create(null, ""))
                .build();
        HttpResponse<CancelMchTransferResponse> httpResponse = httpClient.execute(httpRequest, CancelMchTransferResponse.class);

        return httpResponse.getServiceResponse();
    }

    public CancelMchTransferResponse cancelMchTransferOkhttp(CancelMchTransferRequest request) {
        String HOST = "https://api.mch.weixin.qq.com";
        String METHOD = "POST";
        String uri = "/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel";
        uri = uri.replace("{out_bill_no}", WXPayUtility.urlEncode(request.getOutBillNo()));

        Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
        reqBuilder.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        reqBuilder.addHeader(Constant.WECHAT_PAY_SERIAL, encryptor.getWechatpaySerial());
        reqBuilder.addHeader(Constant.AUTHORIZATION, WXPayUtility.buildAuthorization(request.getMchId(), request.getSerialNumber(), WXPayUtility.loadPrivateKeyFromPath(request.getCertKeyPath()), METHOD, uri, null));
        reqBuilder.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        okhttp3.RequestBody emptyBody = okhttp3.RequestBody.create(null, "");
        reqBuilder.method(METHOD, emptyBody);
        Request httpRequest = reqBuilder.build();

        // 发送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                // 2XX 成功，验证应答签名
//                WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, httpResponse.headers(), respBody);
                // 从HTTP应答报文构建返回数据
                return WXPayUtility.fromJson(respBody, CancelMchTransferResponse.class);
            } else {
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }

    /**
     * 微信单号申请电子回单
     *
     * @param request 请求参数
     * @return AcceptElecsignResponse
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public AcceptElecsignResponse acceptElecsignByNo(AcceptElecsignByNoRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/transfer-bill-no";

        AcceptElecsignByNoRequest realRequest = request;

        if (this.hostName != null) {
            requestPath = requestPath.replaceFirst(HostName.API.getValue(), hostName.getValue());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.WECHAT_PAY_SERIAL, encryptor.getWechatpaySerial());
        HttpRequest httpRequest = new HttpRequest.Builder()
                .httpMethod(HttpMethod.POST)
                .url(requestPath)
                .headers(headers)
                .body(createRequestBody(realRequest))
                .build();
        HttpResponse<AcceptElecsignResponse> httpResponse = httpClient.execute(httpRequest, AcceptElecsignResponse.class);
        return httpResponse.getServiceResponse();
    }

    /**
     * 微信单号查询电子回单
     *
     * @param request 请求参数
     * @return ElecsignEntity
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public ElecsignEntity queryElecsignByNo(QueryElecsignByNoRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/transfer-bill-no/{transfer_bill_no}";

        // 添加 path param
        requestPath = requestPath.replace("{" + "transfer_bill_no" + "}", urlEncode(request.getTransferBillNo()));

        if (this.hostName != null) {
            requestPath = requestPath.replaceFirst(HostName.API.getValue(), hostName.getValue());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest = new HttpRequest.Builder()
                .httpMethod(HttpMethod.GET)
                .url(requestPath)
                .headers(headers)
                .build();
        HttpResponse<ElecsignEntity> httpResponse = httpClient.execute(httpRequest, ElecsignEntity.class);
        return httpResponse.getServiceResponse();
    }

    /**
     * 商户单号申请电子回单
     *
     * @param request 请求参数
     * @return AcceptElecsignResponse
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public AcceptElecsignResponse acceptElecsignByOutNo(AcceptElecsignByOutNoRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/out-bill-no";

        AcceptElecsignByOutNoRequest realRequest = request;

        if (this.hostName != null) {
            requestPath = requestPath.replaceFirst(HostName.API.getValue(), hostName.getValue());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.WECHAT_PAY_SERIAL, encryptor.getWechatpaySerial());
        HttpRequest httpRequest = new HttpRequest.Builder()
                .httpMethod(HttpMethod.POST)
                .url(requestPath)
                .headers(headers)
                .body(createRequestBody(realRequest))
                .build();
        HttpResponse<AcceptElecsignResponse> httpResponse = httpClient.execute(httpRequest, AcceptElecsignResponse.class);
        return httpResponse.getServiceResponse();
    }

    /**
     * 商户单号查询电子回单
     *
     * @param request 请求参数
     * @return ElecsignEntity
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public ElecsignEntity queryElecsignByOutNo(QueryElecsignByOutNoRequest request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/elecsign/out-bill-no/{out_bill_no}";

        // 添加 path param
        requestPath = requestPath.replace("{" + "out_bill_no" + "}", urlEncode(request.getOutBillNo()));

        if (this.hostName != null) {
            requestPath = requestPath.replaceFirst(HostName.API.getValue(), hostName.getValue());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest = new HttpRequest.Builder()
                .httpMethod(HttpMethod.GET)
                .url(requestPath)
                .headers(headers)
                .build();
        HttpResponse<ElecsignEntity> httpResponse = httpClient.execute(httpRequest, ElecsignEntity.class);
        return httpResponse.getServiceResponse();
    }

    /**
     * 下载电子回单。从 DigestElecsignEntity 得到电子回单的流，应在消费完后验证摘要。
     *
     * @param entity 电子回单Entity
     * @return DigestElecsignEntity
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功，验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功，服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功，content-type不为application/json、解析返回体失败。
     */
    public DigestElecsignEntity downloadElecsign(ElecsignEntity entity) {
        InputStream stream = httpClient.download(entity.getDownloadUrl());
        return new DigestElecsignEntity(stream, entity.getHashValue(), entity.getHashType());
    }

    private RequestBody createRequestBody(Object request) {
        return new JsonRequestBody.Builder().body(toJson(request)).build();
    }
}
